Run each of the following expressions, and try to explain what’s happening in each case. Note that the semicolon in some of these is being used as a statement separator, to squeeze multiple statements onto a single line: for example, X=1;X assigns and then prints a variable. Also remember that a comma between expressions usually builds a tuple, even if there are no enclosing parentheses: X,Y,Z is a three-item tuple, which Python prints back to you in parentheses.
In [ ]:
2 ** 16
In [ ]:
2 / 5, 2 / 5.0
In [ ]:
"spam" + "eggs"
In [ ]:
S = "ham"
"eggs " + S
In [ ]:
S * 5
In [ ]:
S[:0]
In [ ]:
"green %s and %s" % ("eggs", S)
In [ ]:
'green {0} and {1}'.format('eggs', S)
In [ ]:
('x',)[0]
In [ ]:
('x', 'y')[1]
In [ ]:
L = [1,2,3] + [4,5,6]
L, L[:], L[:0], L[-2], L[-2:]
In [ ]:
([1,2,3] + [4,5,6])[2:4]
In [ ]:
[L[2], L[3]]
In [ ]:
L.reverse(); L
In [ ]:
L.sort(); L
In [ ]:
L.index(4)
In [ ]:
{'a':1, 'b':2}['b']
In [ ]:
D = {'x':1, 'y':2, 'z':3}
D['w'] = 0
D
In [ ]:
D['x'] + D['w']
D
In [ ]:
D[(1,2,3)] = 4
D
In [ ]:
list(D.keys()), list(D.values()), (1,2,3) in D
In [ ]:
[[]], ["",[],(),{},None]
At the interactive prompt, define a list named L that contains four strings or numbers (e.g., L=[0,1,2,3] ). Then, experiment with the following boundary cases. You may never see these cases in real programs (especially not in the bizarre ways they appear here!), but they are intended to make you think about the underlying model, and some may be useful in less artificial forms—slicing out of bounds can help, for example, if a sequence is as long as you expect:
In [ ]:
Define another list L with four items, and assign an empty list to one of its offsets (e.g., L[2]=[] ). What happens? Then, assign an empty list to a slice ( L[2:3]=[] ). What happens now? Recall that slice assignment deletes the slice and inserts the new value where it used to be. The del statement deletes offsets, keys, attributes, and names. Use it on your list to delete an item (e.g., del L[0] ). What happens if you delete an entire slice ( del L[1:] )? What happens when you assign a nonsequence to a slice ( L[1:2]=1 )?
In [ ]:
In [ ]:
X = 'spam'
Y = 'eggs'
X, Y = Y, X
In [ ]:
D = {}
D[1] = 'a'
D[2] = 'b'
D
Does the following shed any light on the subject? (Hint: strings, integers, and tuples share which type category?)
In [ ]:
D[(1, 2, 3)] = 'c'
D
Create a dictionary named D with three entries, for keys 'a' , 'b' , and 'c' . What happens if you try to index a nonexistent key ( D['d'] )? What does Python do if you try to assign to a nonexistent key 'd' (e.g., D['d']='spam' )? How does this compare to out-of-bounds assignments and references for lists? Does this sound like the rule for variable names?
In [ ]:
Run interactive tests to answer the following questions:
In [ ]:
Define a string S of four characters: S = "spam" . Then type the following expression: S[0][0][0][0][0] . Any clue as to what’s happening this time? (Hint: recall that a string is a collection of characters, but Python characters are one-character strings.) Does this indexing expression still work if you apply it to a list such as ['s', 'p', 'a', 'm'] ? Why?
In [ ]:
In [ ]:
Write a data structure that represents your personal information: name (first, middle, last), age, job, address, email address, and phone number. You may build the data structure with any combination of built-in object types you like (lists, tuples, dictionaries, strings, numbers). Then, access the individual components of your data structures by indexing. Do some structures make more sense than others for this object?
In [ ]:
Write a script that creates a new output file called myfile.txt and writes the string "Hello file world!" into it. Then write another script that opens my- file.txt and reads and prints its contents. Does the new file show up in the directory where you ran your scripts? What if you add a different directory path to the filename passed to open ? Note: file write methods do not add newline characters to your strings; add an explicit \n at the end of the string if you want to fully terminate the line in the file.
In [ ]:
In [ ]:
In [ ]:
!ls
In [ ]:
In [ ]:
for i in range(5):
print('hello %d\n\a' % i, end="")
In [ ]:
Consider the following code, which uses a while loop and found flag to search a list of powers of 2 for the value of 2 raised to the fifth power (32).
L = [1, 2, 4, 8, 16, 32, 64]
X = 5
found = False
i = 0
while not found and i < len(L):
if 2 ** X == L[i]:
found = True
else:
i = i+1
if found:
print('at index', i)
else:
print(X, 'not found')
As is, the example doesn’t follow normal Python coding techniques. Follow the steps outlined here to improve it:
In [ ]:
Deeper thoughts:
map(lambda x: 2 ** x, range(7)). Try typing this code interactively; we’ll meet lambda more formally in the next part of this book, especially in Chapter 19. Would a list comprehension help here (see Chapter 14)?
In [ ]:
If you haven’t already done so, experiment with making the code changes suggested in this chapter’s sidebar “Changing PyDoc’s Colors” on page 456. Much of the work of real software development is in changing existing code, so the sooner you begin doing so, the better. For reference, my edited copy of PyDoc is in the book’s examples package, named mypydoc.py; to see how it differs, you can run a file compare (fc on Windows) with the original pydoc.py in 3.3 (also included, lest it change radically in 3.4 as the sidebar describes). If PyDoc is more easily customized by the time you read these words, customize colors per its current convention instead; if this involves changing a CSS file, let’s hope the procedure will be well documented in Python’s manuals.
At the Python interactive prompt, write a function that prints its single argument to the screen and call it interactively, passing a variety of object types: string, integer, list, dictionary. Then, try calling it without passing any argument. What happens? What happens when you pass two arguments?
In [ ]:
Write a function called adder in a Python module file. The function should accept two arguments and return the sum (or concatenation) of the two. Then, add code at the bottom of the file to call the adder function with a variety of object types (two strings, two lists, two floating points), and run this file as a script from the system command line. Do you have to print the call statement results to see results on your screen?
In [ ]:
Generalize the adder function you wrote in the last exercise to compute the sum of an arbitrary number of arguments, and change the calls to pass more or fewer than two arguments. What type is the return value sum? (Hints: a slice such as S[:0] returns an empty sequence of the same type as S , and the type built- in function can test types; but see the manually coded min examples in Chapter 18 for a simpler approach.) What happens if you pass in arguments of different types? What about passing in dictionaries?
In [ ]:
Change the adder function from exercise 2 to accept and sum/concatenate three arguments: def adder(good, bad, ugly). Now, provide default values for each argument, and experiment with calling the function interactively. Try passing one, two, three, and four arguments. Then, try passing keyword arguments. Does the call adder(ugly=1, good=2) work? Why? Finally, generalize the new adder to accept and sum/concatenate an arbitrary number of keyword arguments. This is similar to what you did in exercise 3, but you’ll need to iterate over a dictionary, not a tuple. (Hint: the dict.keys method returns a list you can step through with a for or while , but be sure to wrap it in a list call to index it in 3.X; dict.values may help here too.)
In [ ]:
Write a function called copyDict(dict) that copies its dictionary argument. It should return a new dictionary containing all the items in its argument. Use the dictionary keys method to iterate (or, in Python 2.2 and later, step over a dictionary’s keys without calling keys ). Copying sequences is easy ( X[:] makes a top-level copy); does this work for dictionaries, too? As explained in this exercise’s solution, because dictionaries now come with similar tools, this and the next exercise are just coding exercises but still serve as representative function examples.
In [ ]:
Write a function called addDict(dict1, dict2) that computes the union of two dictionaries. It should return a new dictionary containing all the items in both its arguments (which are assumed to be dictionaries). If the same key appears in both arguments, feel free to pick a value from either. Test your function by writing it in a file and running the file as a script. What happens if you pass lists instead of dictionaries? How could you generalize your function to handle this case, too? (Hint: see the type built-in function used earlier.) Does the order of the arguments passed in matter?
In [ ]:
In [ ]:
def f1(a, b): print(a, b) # Normal args
def f2(a, *b): print(a, b) # Positional varargs
def f3(a, **b): print(a, b) # Keyword varargs
def f4(a, *b, **c): print(a, b, c) # Mixed modes
def f5(a, b=2, c=3): print(a, b, c) # Defaults
def f6(a, b=2, *c): print(a, b, c) # Defaults and positional varargs
Test the following calls interactively, and try to explain each result; in some cases, you’ll probably need to fall back on the matching algorithm shown in Chapter 18. Do you think mixing matching modes is a good idea in general? Can you think of cases where it would be useful?
In [ ]:
f1(1, 2)
In [ ]:
f1(b=2, a=1)
In [ ]:
f2(1, 2, 3)
In [ ]:
f3(1, x=2, y=3)
In [ ]:
f4(1, 2, 3, x=2, y=3)
In [ ]:
f5(1)
In [ ]:
f5(1, 4)
In [ ]:
f6(1)
In [ ]:
f6(1, 3, 4)
Recall the following code snippet from Chapter 13, which simplistically determines whether a positive integer is prime:
x = y // 2 # For some y > 1
while x > 1:
if y % x == 0: # Remainder
print(y, 'has factor', x)
break # Skip else
x -= 1
else: # Normal exit
print(y, 'is prime')
Package this code as a reusable function, add some calls to the function. While you’re at it, experiment with replacing the first line’s // operator with / to see how true division changes the / operator in Python 3.X and breaks this code (refer back to Chapter 5 if you need a reminder). What can you do about negatives, and the values 0 and 1 ? How about speeding this up? Your outputs should look something like this:
13 is prime
13.0 is prime
15 has factor 5
15.0 has factor 5.0
In [ ]:
Write code to build a new list containing the square roots of all the numbers in this list: [2, 4, 9, 16, 25]. Code this as a for loop first,
then as a map call, then as a list comprehension, and finally as a generator expression. Use the sqrt function in the built-in math module to do the calculation (i.e.,
import math and say math.sqrt(x) ). Of the four, which approach do you like best?
In [ ]:
In Chapter 5, we saw three ways to compute square roots:
math.sqrt(X) , X ** .5 , and pow(X, .5) . If your programs run a lot of these, their
relative performance might become important. To see which is quickest, repurpose
the timerseqs.py script we wrote in this chapter to time each of these three tools.
Use the bestof or bestoftotal functions in one of this chapter’s timer modules to
test (you can use either the original, the 3.X-only keyword-only variant, or the 2.X/
3.X version, and may use Python’s timeit module as well). You might also want
to repackage the testing code in this script for better reusability—by passing a test
functions tuple to a general tester function, for example (for this exercise a copy-
and-modify approach is fine). Which of the three square root tools seems to run
fastest on your machine and Python in general? Finally, how might you go about
interactively timing the speed of dictionary comprehensions versus for loops?
In [ ]:
Write a simple recursion function named countdown that prints
numbers as it counts down to zero. For example, a call countdown(5) will print: 5
4 3 2 1 stop. There’s no obvious reason to code this with an explicit stack or
queue, but what about a nonfunction approach? Would a generator make sense
here?
In [ ]:
Finally, a computer science classic (but demonstrative nonetheless). We employed the notion of factorials in Chapter 20’s coverage of permutations: N! , computed as N*(N-1)*(N-2)*...1 . For instance, 6! is 6*5*4\3\2*1 , or 720 . Code and time four functions that, for a call fact(N) , each return N! . Code these four functions (1) as a recursive countdown per Chapter 19; (2) using the functional reduce call per Chapter 19; (3) with a simple iterative counter loop per Chapter 13; and (4) using the math.factorial library tool per Chapter 20. Use Chapter 21’s timeit to time each of your functions. What conclusions can you draw from your results?
In [ ]: